package com.simple.mqtt.common.tool;

import com.simple.mqtt.common.domain.StrTemplateParser;
import javafx.util.Pair;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * MQTT主题处理工具类
 * 提供主题验证、匹配、解析等功能
 */
public class TopicTool {
    public static final String TOPIC_LAYER = "/";
    public static final String TOPIC_WILDCARDS_ONE = "+";
    public static final String TOPIC_WILDCARDS_MORE = "#";
    public static final Character TOPIC_LAYER_CHARACTER = '/';
    public static final Character TOPIC_WILDCARDS_ONE_CHARACTER = '+';
    public static final Character TOPIC_WILDCARDS_MORE_CHARACTER = '#';

    // ==================== 主题验证方法 ====================

    /**
     * 验证多个主题过滤器
     *
     * @param topicFilterList 主题过滤器列表
     */
    public static void validateTopicFilter(List<String> topicFilterList) {
        for (String topicFilter : topicFilterList) {
            validateTopicFilter(topicFilter);
        }
    }

    /**
     * 验证单个主题过滤器
     *
     * @param topicFilter 主题过滤器
     * @throws IllegalArgumentException 当主题过滤器无效时
     */
    public static void validateTopicFilter(String topicFilter) throws IllegalArgumentException {
        if (!StringUtils.hasText(topicFilter)) {
            throw new IllegalArgumentException("TopicFilter is empty:" + topicFilter);
        }
        char[] topicFilterChars = topicFilter.toCharArray();
        int topicFilterLength = topicFilterChars.length;
        int topicFilterIdxEnd = topicFilterLength - 1;
        char ch;
        for (int i = 0; i < topicFilterLength; i++) {
            ch = topicFilterChars[i];
            if (Character.isWhitespace(ch)) {
                LogTool.warn("注意：主题：[{}] 包含空白字符：[{}]，请检查", topicFilter, ch);
            } else if (ch == TOPIC_WILDCARDS_MORE_CHARACTER) {
                // 验证：#通配符只能在末尾
                if (i < topicFilterIdxEnd) {
                    throw new IllegalArgumentException("Mqtt subscribe topicFilter illegal:" + topicFilter);
                }
            } else if (ch == TOPIC_WILDCARDS_ONE_CHARACTER) {
                // 验证：允许单个+，检查+前面是否有/，后面是否有/
                if ((i > 0 && topicFilterChars[i - 1] != TOPIC_LAYER_CHARACTER) ||
                        (i < topicFilterIdxEnd && topicFilterChars[i + 1] != TOPIC_LAYER_CHARACTER)) {
                    throw new IllegalArgumentException("Mqtt subscribe topicFilter illegal:" + topicFilter);
                }
            }
        }
    }

    /**
     * Validate topic name
     *
     * @param topicName topic name
     * @throws IllegalArgumentException when topic name contains wildcards
     */
    public static void validateTopicName(String topicName) throws IllegalArgumentException {
        if (isTopicFilter(topicName)) {
            throw new IllegalArgumentException("Topic has wildcards char [+] or [#], topicName:" + topicName);
        }
    }

    // ==================== 保留消息主题处理 ====================

    /**
     * 解析保留消息主题
     *
     * @param topicName 主题名称
     * @return Pair<实际主题, 保留时间> 保留时间含义：
     * -1: 主题有问题，丢弃消息
     * 0: 使用原始主题
     * >0: 保留消息存储时间（秒）
     */
    public static Pair<String, Integer> retainTopicName(String topicName) {
        if (topicName.startsWith("$retain/")) {
            return getRetainTopicPair(topicName);
        } else {
            return new Pair<>(topicName, 0);
        }
    }

    /**
     * 处理$retain主题
     *
     * @param topicName 主题名称
     * @return Pair<实际主题, 保留时间>
     */
    private static Pair<String, Integer> getRetainTopicPair(String topicName) {
        // $retain/的长度
        int timeIndexBegin = 8;
        int nextLayer = topicName.indexOf(TOPIC_LAYER, timeIndexBegin);
        if (nextLayer == -1) {
            return new Pair<>(topicName, -1);
        }
        int time;
        try {
            time = Integer.parseInt(topicName.substring(timeIndexBegin, nextLayer));
        } catch (NumberFormatException e) {
            time = -1;
        }
        String retainTopic = topicName.substring(nextLayer + 1);
        if (retainTopic.isEmpty()) {
            return new Pair<>(topicName, -1);
        } else {
            return new Pair<>(retainTopic, time);
        }
    }

    // ==================== 主题匹配方法 ====================

    /**
     * 检查主题过滤器是否匹配主题名称
     *
     * @param topicFilter 主题过滤器（可包含通配符）
     * @param topicName   主题名称（不能包含通配符）
     * @return 是否匹配
     */
    public static boolean match(String topicFilter, String topicName) {
        char[] topicFilterChars = topicFilter.toCharArray();
        char[] topicNameChars = topicName.toCharArray();
        int topicFilterLength = topicFilterChars.length;
        int topicNameLength = topicNameChars.length;
        int topicFilterIdxEnd = topicFilterLength - 1;
        int topicNameIdxEnd = topicNameLength - 1;
        char ch;
        // 是否进入+级通配符
        boolean inLayerWildcard = false;
        int wildcardCharLen = 0;
        topicFilterLoop:
        for (int i = 0; i < topicFilterLength; i++) {
            ch = topicFilterChars[i];
            if (ch == TOPIC_WILDCARDS_MORE_CHARACTER) {
                // 验证：#通配符只能在末尾
                if (i < topicFilterIdxEnd) {
                    throw new IllegalArgumentException("Mqtt subscribe topicFilter illegal:" + topicFilter);
                }
                return true;
            } else if (ch == TOPIC_WILDCARDS_ONE_CHARACTER) {
                // 验证：允许单个+，检查+前面是否有/，后面是否有/
                if ((i > 0 && topicFilterChars[i - 1] != TOPIC_LAYER_CHARACTER) ||
                        (i < topicFilterIdxEnd && topicFilterChars[i + 1] != TOPIC_LAYER_CHARACTER)) {
                    throw new IllegalArgumentException("Mqtt subscribe topicFilter illegal:" + topicFilter);
                }
                // 如果+在末尾，检查topicName是否还有层级/
                // topicName index
                int topicNameIdx = i + wildcardCharLen;
                if (i == topicFilterIdxEnd && topicNameLength > topicNameIdx) {
                    for (int j = topicNameIdx; j < topicNameLength; j++) {
                        if (topicNameChars[j] == TOPIC_LAYER_CHARACTER) {
                            return false;
                        }
                    }
                    return true;
                }
                inLayerWildcard = true;
            } else if (ch == TOPIC_LAYER_CHARACTER) {
                if (inLayerWildcard) {
                    inLayerWildcard = false;
                }
                // Preview next character, if it's # and topicName length is insufficient
                int next = i + 1;
                if ((topicFilterLength > next) &&
                        topicFilterChars[next] == TOPIC_WILDCARDS_MORE_CHARACTER &&
                        topicNameLength < next) {
                    return true;
                }
            }
            // topicName长度不够了
            if (topicNameIdxEnd < i) {
                return false;
            }
            // 进入通配符
            if (inLayerWildcard) {
                for (int j = i + wildcardCharLen; j < topicNameLength; j++) {
                    if (topicNameChars[j] == TOPIC_LAYER_CHARACTER) {
                        wildcardCharLen--;
                        continue topicFilterLoop;
                    } else {
                        wildcardCharLen++;
                    }
                }
            }
            // topicName index
            int topicNameIdx = i + wildcardCharLen;
            // topic 已经完成，topicName 还有数据
            if (topicNameIdx > topicNameIdxEnd) {
                return false;
            }
            if (ch != topicNameChars[topicNameIdx]) {
                return false;
            }
        }
        // 判断 topicName 是否还有数据
        return topicFilterLength + wildcardCharLen + 1 > topicNameLength;
    }

    // ==================== 主题模板处理相关方法 ====================

    /**
     * 获取处理完成之后的 topic，需要考虑 test/${abc}123 也要替换成 test/+ 而非 test/+123
     *
     * @param topicTemplate topic 模板
     * @return 获取处理完成之后的 topic
     */
    public static String getTopicFilter(String topicTemplate) {
        // 替换 ${name} 为 +
        StringTokenizer tokenizer = new StringTokenizer(topicTemplate, TOPIC_LAYER, true);
        String token;
        StringBuilder topicFilterBuilder = new StringBuilder(topicTemplate.length());
        while (tokenizer.hasMoreTokens()) {
            token = tokenizer.nextToken();
            if (TOPIC_LAYER.equals(token)) {
                topicFilterBuilder.append(token);
            } else if (hasVariable(token)) {
                topicFilterBuilder.append(TOPIC_WILDCARDS_ONE);
            } else {
                topicFilterBuilder.append(token);
            }
        }
        return topicFilterBuilder.toString();
    }

    /**
     * 判断是否含有 ${x} 这样的变量
     *
     * @param input input
     * @return 是否含有变量
     */
    public static boolean hasVariable(String input) {
        if (!StringUtils.hasText(input)) {
            return false;
        }
        int startIndex = input.indexOf("${");
        // 检查是否存在 "${"
        if (startIndex == -1) {
            return false;
        }
        int endIndex = input.indexOf('}', startIndex);
        // 检查是否同时存在 "${" 和 "}"，并且 "}" 在 "${" 之后
        return endIndex != -1 && endIndex > startIndex + 2;
    }

    /**
     * 解析 topic 中的变量，变量的格式为 ${x}，x 为 payload 中的字段名
     *
     * @param topicTemplate topic 模板
     * @param payload       payload
     * @return 解析后的 topic
     */
    public static String resolveTopic(String topicTemplate, Object payload) {
        if (payload == null) {
            return topicTemplate;
        }
        // 替换变量
        StringBuilder sb = new StringBuilder((int) (topicTemplate.length() * 1.5));
        int cursor = 0;
        for (int start, end; (start = topicTemplate.indexOf("${", cursor)) != -1 && (end = topicTemplate.indexOf('}', start)) != -1; ) {
            sb.append(topicTemplate, cursor, start);
            String fieldName = topicTemplate.substring(start + 2, end);
            Object value = getFieldValue(payload, fieldName);
            sb.append(value == null ? "" : value);
            cursor = end + 1;
        }
        if (cursor == 0) {
            return topicTemplate;
        } else {
            sb.append(topicTemplate.substring(cursor));
            return sb.toString();
        }
    }

    /**
     * 获取对象字段值
     *
     * @param obj       对象
     * @param fieldName 字段名
     * @return 字段值
     */
    public static Object getFieldValue(Object obj, String fieldName) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to resolve field: " + fieldName + " from payload object", e);
        }
    }

    // ==================== 主题解析相关方法 ====================

    /**
     * 以 / 切分 topic，如果以 / 开头和 / 结尾会多一级，比 split 性能要好
     *
     * @param topic topic
     * @return part 数组
     */
    public static String[] getTopicParts(String topic) {
        // 大部分 topic 层级都在 10 以内
        List<String> tokenList = new ArrayList<>(10);
        char[] topicChars = topic.toCharArray();
        int topicLength = topicChars.length;
        int topicIdxEnd = topicLength - 1;
        char ch;
        // 前一个位置
        int prev = 0;
        for (int i = 0; i < topicLength; i++) {
            ch = topicChars[i];
            if (TOPIC_LAYER_CHARACTER == ch) {
                // 如果 / 为起始和最后的位置，添加 / 进 topic part
                if (i == 0) {
                    tokenList.add(TOPIC_LAYER);
                    prev++;
                } else {
                    tokenList.add(new String(topicChars, prev, i - prev));
                    prev = i;
                    prev++;
                    if (i == topicIdxEnd) {
                        tokenList.add(TOPIC_LAYER);
                    }
                }
            } else {
                if (i == topicIdxEnd) {
                    tokenList.add(new String(topicChars, prev, topicLength - prev));
                }
            }
        }
        return tokenList.toArray(new String[0]);
    }

    /**
     * 解析 topic 模板中的变量，不匹配时返回空 Map
     *
     * <p>
     * 例如 $SYS/brokers/${node}/clients/${clientid}/disconnected 中提取 node 和 clientid
     * </p>
     *
     * @param topicTemplate topicTemplate
     * @param topic         topic
     * @return 获取变量值
     */
    public static Map<String, String> getTopicVars(String topicTemplate, String topic) {
        return StrTemplateParser.parseVariables(topicTemplate, topic);
    }

    /**
     * 判断是否 topic filter
     *
     * @param topicFilter topicFilter
     * @return 是否 topic filter
     */
    public static boolean isTopicFilter(String topicFilter) {
        // 从尾部开始遍历，因为 + # 一般出现在 topicFilter 的尾部
        for (int i = topicFilter.length() - 1; i >= 0; i--) {
            char ch = topicFilter.charAt(i);
            // topic 中有空白符打印提示
            if (Character.isWhitespace(ch)) {
                LogTool.warn("注意：topic:[{}] 中包含空白字符串:[{}]，请检查是否正确", topicFilter, ch);
            } else if (TOPIC_WILDCARDS_ONE_CHARACTER == ch || TOPIC_WILDCARDS_MORE_CHARACTER == ch) {
                return true;
            }
        }
        return false;
    }
}